﻿/*

The MIT License

Copyright (c) 2010 Cartesian Analytics, Inc. 

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

using System;
using System.Diagnostics;

namespace Pipra.Math.Geometry
{
    /// <summary>
    /// A point in 3D space.
    /// </summary>
    public struct Point3 :
        IPoint3<double>,
        IEquatable<Point3>,
        IComparable<Point3>,
        IIntersectable<Point3>,
        IHasDistance<double, Point3>
    {
        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="a">A point.</param>
        /// <param name="b">A point.</param>
        /// <returns>True if both points have the same component values.</returns>
        public static bool operator ==(Point3 a, Point3 b)
        {
            return a.Equals(b);
        }
        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="a">A point.</param>
        /// <param name="b">A point.</param>
        /// <returns>True if both points do not have the same component values.</returns>
        public static bool operator !=(Point3 a, Point3 b)
        {
            return !a.Equals(b);
        }

        /// <summary>
        /// A point with all components set to zero.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public static readonly Point3 ZeroPoint = new Point3(0, 0, 0);

        public static Point3 Transform(Point3 row, MatrixOrder3 rightMatrix)
        {
            return new Point3(
                (
                    (row.X * rightMatrix.E00)
                    + (row.Y * rightMatrix.E10)
                    + (row.Z * rightMatrix.E20)
                ),
                (
                    (row.X * rightMatrix.E01)
                    + (row.Y * rightMatrix.E11)
                    + (row.Z * rightMatrix.E21)
                ),
                (
                    (row.X * rightMatrix.E02)
                    + (row.Y * rightMatrix.E12)
                    + (row.Z * rightMatrix.E22)
                )
            );
        }

        public static Point3 Transform(MatrixOrder3 leftMatrix, Point3 column)
        {
            return new Point3(
                (
                    (leftMatrix.E00 * column.X)
                    + (leftMatrix.E01 * column.Y)
                    + (leftMatrix.E02 * column.Z)
                ),
                (
                    (leftMatrix.E10 * column.X)
                    + (leftMatrix.E11 * column.Y)
                    + (leftMatrix.E12 * column.Z)
                ),
                (
                    (leftMatrix.E20 * column.X)
                    + (leftMatrix.E21 * column.Y)
                    + (leftMatrix.E22 * column.Z)
                )
            );
        }

        /// <summary>
        /// The X componenet of this point.
        /// </summary>
        public readonly double X;
        /// <summary>
        /// The Y componenet of this point.
        /// </summary>
        public readonly double Y;
        /// <summary>
        /// The Z componenet of this point.
        /// </summary>
        public readonly double Z;
        /// <summary>
        /// Creates a point with the given <paramref name="x"/>, <paramref name="y"/>, and <paramref name="z"/> coordinates.
        /// </summary>
        /// <param name="x">A coordinate.</param>
        /// <param name="y">A coordinate.</param>
        /// <param name="z">A coordinate.</param>
        public Point3(double x, double y, double z) {
			X = x;
			Y = y;
			Z = z;
		}
        /// <summary>
        /// Creates a point with the same cordinates as the given <paramref name="point"/>.
        /// </summary>
        /// <param name="point">A coordinate triple.</param>
		public Point3(ICoordinateTriple<double> point) {
            X = point.X;
            Y = point.Y;
            Z = point.Z;
		}

        /// <summary>
        /// The X componenet of this point.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        double ICoordinateTriple<double>.X
        {
            get { return X; }
        }
        /// <summary>
        /// The Y componenet of this point.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        double ICoordinateTriple<double>.Y
        {
            get { return Y; }
        }
        /// <summary>
        /// The Z componenet of this point.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        double ICoordinateTriple<double>.Z
        {
            get { return Z; }
        }
        /// <summary>
        /// Calculates the distance between this and the given <paramref name="point"/>
        /// </summary>
        /// <param name="point">The point to calculate distance to.</param>
        /// <returns>The distance.</returns>
        public double Distance(Point3 point)
        {
            double dx = point.X - X;
            double dy = point.Y - Y;
            double dz = point.Z - Z;
            return System.Math.Sqrt(
                (dx * dx)
                +
                (dy * dy)
                +
                (dz * dz)
            );
        }
        /// <summary>
        /// Calculates the distance between this and the given <paramref name="point"/>
        /// </summary>
        /// <param name="point">The point to calculate distance to.</param>
        /// <returns>The distance.</returns>
        public double Distance(IPoint3<double> point)
        {
            double dx = point.X - X;
            double dy = point.Y - Y;
            double dz = point.Z - Z;
            return System.Math.Sqrt(
                (dx * dx)
                +
                (dy * dy)
                +
                (dz * dz)
            );
        }
        /// <summary>
        /// Calculates the squared distance between this and the given <paramref name="point"/>
        /// </summary>
        /// <param name="point">The point to calculate squared distance to.</param>
        /// <returns>The squared distance.</returns>
        public double DistanceSquared(Point3 point)
        {
            double dx = point.X - X;
            double dy = point.Y - Y;
            double dz = point.Z - Z;
            return (
                (dx * dx)
                +
                (dy * dy)
                +
                (dz * dz)
            );
        }
        /// <summary>
        /// Calculates the squared distance between this and the given <paramref name="point"/>
        /// </summary>
        /// <param name="point">The point to calculate squared distance to.</param>
        /// <returns>The squared distance.</returns>
        public double DistanceSquared(IPoint3<double> point)
        {
            double dx = point.X - X;
            double dy = point.Y - Y;
            double dz = point.Z - Z;
            return (
                (dx * dx)
                +
                (dy * dy)
                +
                (dz * dz)
            );
        }
        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that is the hash code for this instance.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override int GetHashCode()
        {
            return X.GetHashCode();
        }
        /// <summary>
        /// Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <returns>
        /// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
        /// </returns>
        /// <param name="obj">Another object to compare to.</param><filterpriority>2</filterpriority>
        public override bool Equals(object obj)
        {
            return null != obj
                && (
                        (obj is Point3 && Equals((Point3)obj))
                )
            ;
        }
        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return String.Concat(X, ',', Y, ',', Z);
        }
        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public bool Equals(Point3 other)
        {
            return X == other.X && Y == other.Y && Z == other.Z;
        }
        /// <summary>
        /// Indicates whether the current object is equal to another object.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public bool Equals(ICoordinateTriple<double> other)
        {
            return !ReferenceEquals(null,other) && X == other.X && Y == other.Y && Z == other.Z;
        }
        /// <summary>
        /// Compares the current object with another object of the same type.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings: 
        ///                     Value 
        ///                     Meaning 
        ///                     Less than zero 
        ///                     This object is less than the <paramref name="other"/> parameter.
        ///                     Zero 
        ///                     This object is equal to <paramref name="other"/>. 
        ///                     Greater than zero 
        ///                     This object is greater than <paramref name="other"/>. 
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public int CompareTo(Point3 other)
        {
            int c = X.CompareTo(other.X);
            if (0 == c)
            {
                c = Y.CompareTo(other.Y);
                return 0 == c ? Z.CompareTo(Z) : c;
            }
            return c;
        }
        /// <summary>
        /// Compares the current object with another object of the same type.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings: 
        ///                     Value 
        ///                     Meaning 
        ///                     Less than zero 
        ///                     This object is less than the <paramref name="other"/> parameter.
        ///                     Zero 
        ///                     This object is equal to <paramref name="other"/>. 
        ///                     Greater than zero 
        ///                     This object is greater than <paramref name="other"/>. 
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public int CompareTo(ICoordinateTriple<double> other)
        {
            if (null == other)
            {
                return 1;
            }
            int c = X.CompareTo(other.X);
            if (0 == c)
            {
                c = Y.CompareTo(other.Y);
                return 0 == c ? Z.CompareTo(Z) : c;
            }
            return c;
        }
        /// <summary>
        /// Determines the difference between this point and another <paramref name="point"/>.
        /// </summary>
        /// <param name="point">The point to take the difference from this point.</param>
        /// <returns>The vector difference.</returns>
        public Vector3 Difference(Point3 point)
        {
            return new Vector3(
                X - point.X,
                Y - point.Y,
                Z - point.Z
            );
        }
        /// <summary>
        /// Determines the difference between this point and another <paramref name="point"/>.
        /// </summary>
        /// <param name="point">The point to take the difference from this point.</param>
        /// <returns>The vector difference.</returns>
        public Vector3 Difference(ICoordinateTriple<double> point)
        {
            return new Vector3(
                X - point.X,
                Y - point.Y,
                Z - point.Z
            );
        }
        /// <summary>
        /// Determines the difference between this point and another <paramref name="point"/>.
        /// </summary>
        /// <param name="point">The point to take the difference from this point.</param>
        /// <returns>The vector difference.</returns>
        IVector3<double> IPoint3<double>.Difference(ICoordinateTriple<double> point)
        {
            return new Vector3(
                X - point.X,
                Y - point.Y,
                Z - point.Z
            );
        }
        /// <summary>
        /// Adds an offset vector to this point.
        /// </summary>
        /// <param name="delta">The offset vector.</param>
        /// <returns>A point offset from this point by a <paramref name="delta"/>.</returns>
        public Point3 Add(Vector3 delta)
        {
            return new Point3(
                X + delta.X,
                Y + delta.Y,
                Z + delta.Z
            );
        }
        /// <summary>
        /// Adds an offset vector to this point.
        /// </summary>
        /// <param name="delta">The offset vector.</param>
        /// <returns>A point offset from this point by a <paramref name="delta"/>.</returns>
        public Point3 Add(ICoordinateTriple<double> delta)
        {
            return new Point3(
                X + delta.X,
                Y + delta.Y,
                Z + delta.Z
            );
        }
        /// <summary>
        /// Adds an offset vector to this point.
        /// </summary>
        /// <param name="delta">The offset vector.</param>
        /// <returns>A point offset from this point by a <paramref name="delta"/>.</returns>
        IPoint3<double> IPoint3<double>.Add(ICoordinateTriple<double> delta)
        {
            return new Point3(
                X + delta.X,
                Y + delta.Y,
                Z + delta.Z
            );
        }
        /// <summary>
        /// Determines if this point intersects another.
        /// </summary>
        /// <param name="p">A point to test intersection with.</param>
        /// <returns>True when another point intersects this point.</returns>
        bool IIntersectable<IPoint3<double>>.Intersects(IPoint3<double> p)
        {
            return Equals(p);
        }
        /// <summary>
        /// Determines if this point intersects another.
        /// </summary>
        /// <param name="p">A point to test intersection with.</param>
        /// <returns>True when another point intersects this point.</returns>
        bool IIntersectable<Point3>.Intersects(Point3 p)
        {
            return Equals(p);
        }
    }
}
